package com.google.code.xconf.schema.definition;

import com.google.code.xconf.Property;
import com.google.code.xconf.PropertyGroup;
import com.google.code.xconf.schema.SchemaDefinition;
import com.google.code.xconf.schema.SchemaLayoutException;
import com.google.code.xconf.schema.SchemaSettings;
import com.google.code.xconf.schema.SchemaValidationException;
import static com.google.code.xconf.schema.SchemaConstants.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PropertiesDefinition extends SchemaDefinition {

    private List<PropertyDefinition> propertyDefinitions = new ArrayList<>();

    public PropertiesDefinition(PropertyGroup group, SchemaSettings schemaSettings) {
        super(PROPERTIES_DEFINITION, group, schemaSettings);
        configure();
    }

    public void configure() {
        settings = settings.snapshot();
        settings.setPropertiesStrictOrdering(resolvePropertiesStrictOrdering());
        propertyDefinitions.addAll(resolvePropertyDefinitions());
    }

    @Override
    protected List<String> getValidNodeNames() {
        return Arrays.asList(PROPERTIES_STRICT_ORDERING, PROPERTY, PROPERTY_GROUP);
    }

    public void validate(Collection<Property> properties) throws SchemaValidationException {
        Set<String> knownPropertyNames = new HashSet<>();
        int propertyIndex = 0;
        for (Property property : properties) {
            if (!isSameNamedPropertiesAllowed() && knownPropertyNames.contains(property.getName())) {
                throw new SchemaValidationException("Duplicate property identifier found for property '" + property.getName() + "'. Define 'SameNamedPropertiesAllowed true' to allow duplicate property name declarations");
            }
            if (SCHEMA_LOCATION.equals(property.getName())) {
                continue;
            }
            PropertyDefinition definition = getPropertyDefinition(property, propertyIndex, isPropertyStrictOrdering());
            if (definition == null) {
                throw new SchemaValidationException((property instanceof PropertyGroup ? PROPERTY_GROUP : PROPERTY) + " '" + property.getName() + "' doesn't match schema layout.");
            }
            definition.validate(property);
            knownPropertyNames.add(property.getName());
            propertyIndex++;
        }
    }

    private PropertyDefinition getPropertyDefinition(Property property, int index, boolean strictOrdering) {
        String propertyName = property.getName();
        boolean isGroup = property instanceof PropertyGroup;
        for (int i = 0; i < propertyDefinitions.size(); i++) {
            PropertyDefinition def = propertyDefinitions.get(i);
            if (strictOrdering && i != index) {
                continue;
            }
            if (def.isGroup() != isGroup) {
                continue;
            }
            if (def.matchIdentifier(propertyName)) {
                return def;
            }
        }
        return null;
    }

    private boolean resolvePropertiesStrictOrdering() {
        if (definition.containsValue(PROPERTIES_STRICT_ORDERING)) {
            return true;
        }
        return resolveDefinitionPropertyBoolean(PROPERTIES_STRICT_ORDERING, true, isPropertyStrictOrdering());
    }

    private Collection<PropertyDefinition> resolvePropertyDefinitions() {
        Collection<PropertyDefinition> result = new ArrayList<>();
        for (PropertyGroup group : definition.getPropertyGroups()) {
            boolean isGroup;
            if (group.getName().equals(PROPERTY)) {
                isGroup = false;
            } else if (group.getName().equals(PROPERTY_GROUP)) {
                isGroup = true;
            } else {
                throw new SchemaLayoutException("Property group '" +  group.getName() + "' not allowed here");
            }
            result.add(new PropertyDefinition(group, settings, isGroup));
        }
        return result;
    }

}
